package com.gitee.feizns.dynamic.convert.impl;

import com.gitee.feizns.dynamic.convert.AbstractConverter;
import com.gitee.feizns.dynamic.reflect.ClassUtils;
import com.gitee.feizns.dynamic.reflect.MethodUtils;

import java.lang.reflect.Method;
import java.util.Deque;
import java.util.Optional;

/**
 * <b>前缀实例方法转换器实现类</b>
 * <p>
 *     会在目标类型中查找一个单参数的静态方法，
 *     该方法是原数据（source）可以访问的方法
 *     （仅支持非基础类型方法）
 * </p>
 * <pre>{@code
 *     public static class Admin {}
 *     public static class User {
 *         public User toAdmin() {
 *             return new User();
 *         }
 *     }
 *     public static void main(String[] args) {
 *         PrefixMethodConverter to = new PrefixMethodConverter("to");
 *         Object to1 = to.to(new User(), (Class<Object>) (Class<?>) Admin.class);
 *         System.out.println(to1);
 *         System.out.println(to1.getClass());
 *     }
 * }</pre>
 * @author feizns
 * @since 2023/1/14
 */
public class PrefixMethodConverter extends AbstractConverter<Object, Object> {

    /**
     * 转换的方法名（仅支持非基础类型方法）
     */
    private final String methodNamePrefix;

    public PrefixMethodConverter(String methodNamePrefix) {
        this.methodNamePrefix = methodNamePrefix;
    }

    @Override
    public Object to(Object source, Class<Object> targetType) {
        if ( source != null && !targetType.isAssignableFrom(source.getClass()) ) {
            Method method = findMethod(source.getClass(), targetType.getSimpleName());
            if ( method != null && method.getReturnType().isAssignableFrom(targetType) )
                return MethodUtils.invoke(source, method);
            return null;
        }
        return source;
    }

    @Override
    public boolean support(Object source, Class<?> targetType) {
        return source != null && Optional.ofNullable(findMethod(source.getClass(), targetType.getSimpleName())).isPresent();
    }

    /**
     * <b>获取对应前缀的方法</b>
     * @param type {@link Class}
     * @param methodName 方法名称
     * @return {@link Method}
     * @throws NullPointerException type为空的情况.
     */
    public Method findMethod(Class<?> type, String methodName) {
        Deque<Class<?>> classesAndInterfaces = ClassUtils.getClassesAndInterfaces(type);
        while ( !classesAndInterfaces.isEmpty() ) {
            Class<?> methodInType = classesAndInterfaces.remove();
            Method method = MethodUtils.find(methodInType, methodNamePrefix + methodName);
            if ( method != null )
                return method;
        }
        return null;
    }

}
